
#include "session.h"

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <arch_irq.h>

#include <asm/shdev.h>
#endif
#include <asm/delay.h>

#include <uapi/stdlib.h>
#include <uapi/string.h>

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <asm-generic/delay.h>
#include <ff/ff.h>
static struct SwsContext *imgCtx = NULL;
static AVFrame *yuvFrame = NULL;
typedef char FF_TCHAR;
#else
#include <mve/cv/cv_global.h>
#include "ff.h"

#ifdef CONFIG_USR_SOC_MVE_CODEC_INSIDE_QUICKZ
#include <system/quicklz.h>
#endif
#endif

//////////////////////////////////////////////////////////////////////////////////////////
// Mali normal decoder
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
typedef char FF_TCHAR;
#endif
/**
 * SB list function
 */

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
static int GetFsDriverPath(storage_type_t type, FF_TCHAR path[])
{
	if (type == STORAGE_TYPE_USB0) {
		path[0] = 'u';
		path[1] = 's';
		path[2] = 'b';
		path[3] = '0';
		path[4] = ':';
		path[5] = 0;
		return 5;
	}

	else if (type == STORAGE_TYPE_USB1) {
		path[0] = 'u';
		path[1] = 's';
		path[2] = 'b';
		path[3] = '1';
		path[4] = ':';
		path[5] = 0;
		return 5;
	}

	else if (type == STORAGE_TYPE_NETFS) {
		path[0] = 'n';
		path[1] = 'e';
		path[2] = 't';
		path[3] = 'f';
		path[4] = 's';
		path[5] = ':';
		path[6] = 0;
		return 6;
	}

	return 0;

}

/* Full file name */
static int GetFsFullPathName(storage_type_t type, FF_TCHAR *pFull, int maxl, const FF_TCHAR *name)
{
	int res = GetFsDriverPath(type, pFull);
	if (res < 0)
		return res;

	if (name != NULL) {
		int j = 0;
		while (res < maxl && name[j]) {
			pFull[res++] = name[j++];
		}
		pFull[res] = 0;
	}

	return res;
}
#endif

//////////////////////////////////////////////////////////////////////////////////////////

typedef struct
{
	struct SESSION *session, *session_display;

	session_type_t streamtype;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	psysbuf_t in_buf, loadbuf_video_play;
	AVCodecParserContext *parser;
	AVCodecContext *c;
	AVFrame *frame;
	AVPacket *pkt;
	int codec_type;
	AVCodec *codec;
#else
	cv_firmware_t firmtype;
#endif	

	int cvstate;
	int dumpcnt;

	long load_size, load_offset, load_szcount;

	psysbuf_t load_buf;

	FIL file;
	storage_type_t ftype;
	FF_TCHAR filename[MAX_PATH];
#ifdef MEDIA_AV_SYNC
	session_av_sync *sync;
#endif

}session_unit_t;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
//////////////////////////////////////////////////////////////////////////////////////////

#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_H264DEC)
#include "../../mve/firmware/h264dec.c"
#endif
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_HEVCDEC)
#include "../../mve/firmware/hevcdec.c"
#endif
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_JPEGDEC)
#include "../../mve/firmware/jpegdec.c"
#endif
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_MPEG2DEC)
#include "../../mve/firmware/mpeg2dec.c"
#endif
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_MPEG4DEC)
#include "../../mve/firmware/mpeg4dec.c"
#endif
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_RVDEC)
#include "../../mve/firmware/rvdec.c"
#endif
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_VC1DEC)
#include "../../mve/firmware/vc1dec.c"
#endif
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_VP8DEC)
#include "../../mve/firmware/vp8dec.c"
#endif

#ifndef CONFIG_USR_SOC_MVE_CODEC_INSIDE
static const FF_TCHAR *fw_name[FW_TYPE_COUNT] =
{
	L"\0",
	L"h264dec.fwb",
	L"hevcdec.fwb",
	L"jpegdec.fwb",
	L"mpeg2dec.fwb",
	L"mpeg4dec.fwb",
	L"rvdec.fwb",
	L"vc1dec.fwb",
	L"vp8dec.fwb",
};
#endif

static cv_firmware_t decode_get_firmwaretype(int type)
{
	cv_firmware_t firmtype;
	switch (type) {
	case SST_JPEG: firmtype = FW_TYPE_JPEGDEC;  break;
	case SST_H264: firmtype = FW_TYPE_H264DEC;  break;
	case SST_HEVC: firmtype = FW_TYPE_HEVCDEC;  break;
	case SST_MPG2: firmtype = FW_TYPE_MPEG2DEC; break;
	case SST_MPG4: firmtype = FW_TYPE_MPEG4DEC; break;
	case SST_RV:   firmtype = FW_TYPE_RVDEC;    break;
	case SST_VC1:  firmtype = FW_TYPE_VC1DEC;   break;
	case SST_VP8:  firmtype = FW_TYPE_VP8DEC;   break;
	default:
		return FW_TYPE_UNKNOWN;
	};
	return firmtype;
}

static cv_firmware_t decode_load_firmware(session_unit_t *punit)
{
	uint32_t addr = HWADDR(g_cv_map[CV_MAP_FIRMWARE].start) + MMU_PAGE_SIZE;

#ifdef CONFIG_USR_SOC_MVE_CODEC_INSIDE
	const uint8_t *fwbin = NULL;
	long fwbin_size = 0;
#else
	FRESULT fresult = FR_INVALID_NAME;
	FF_TCHAR filename[MAX_PATH];
#endif

	cv_firmware_t firmtype = decode_get_firmwaretype(punit->streamtype);
	if ( firmtype == FW_TYPE_UNKNOWN)
		return firmtype;

#ifdef CONFIG_USR_SOC_MVE_CODEC_INSIDE

	switch (firmtype) {
	case FW_TYPE_H264DEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_H264DEC)
		fwbin      = mvefw_h264dec;
		fwbin_size = mvefw_h264dec_size;
#endif
	}break;
	case FW_TYPE_HEVCDEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_HEVCDEC)
		fwbin      = mvefw_hevcdec;
		fwbin_size = mvefw_hevcdec_size;
#endif
	}break;
	case FW_TYPE_JPEGDEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_JPEGDEC)
		fwbin      = mvefw_jpegdec;
		fwbin_size = mvefw_jpegdec_size;
#endif
	}break;
	case FW_TYPE_MPEG2DEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_MPEG2DEC)
		fwbin      = mvefw_mpeg2dec;
		fwbin_size = mvefw_mpeg2dec_size;
#endif
	}break;
	case FW_TYPE_MPEG4DEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_MPEG4DEC)
		fwbin      = mvefw_mpeg4dec;
		fwbin_size = mvefw_mpeg4dec_size;
#endif
	}break;
	case FW_TYPE_RVDEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_RVDEC)
		fwbin      = mvefw_rvdec;
		fwbin_size = mvefw_rvdec_size;
#endif
	}break;
	case FW_TYPE_VC1DEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_VC1DEC)
		fwbin      = mvefw_vc1dec;
		fwbin_size = mvefw_vc1dec_size;
#endif
	}break;
	case FW_TYPE_VP8DEC:
	{
#if defined(CONFIG_USR_SOC_MVE_CODEC_INSIDE_VP8DEC)
		fwbin      = mvefw_vp8dec;
		fwbin_size = mvefw_vp8dec_size;
#endif
	}break;
	};

	if (fwbin == NULL || fwbin_size < 1) {
		return FW_TYPE_UNKNOWN;
	}

#ifdef CONFIG_USR_SOC_MVE_CODEC_INSIDE_QUICKZ
	if (fwbin_size > 1) {
		size_t szr;
		qlz_state_decompress *decompress_info = (qlz_state_decompress *)malloc(sizeof(qlz_state_decompress));
		if (decompress_info == NULL) {
			return FW_TYPE_UNKNOWN;
		}
		memset(decompress_info, 0, sizeof(qlz_state_decompress));
		szr = qlz_decompress(fwbin, (void*)addr, decompress_info);
		if (szr < 1) {
			firmtype = FW_TYPE_UNKNOWN;
		}
		free((void*)decompress_info);
	}
#else
	memcpy((void*)addr, fwbin, fwbin_size);
#endif

#else // CONFIG_USR_SOC_MVE_CODEC_INSIDE

	if (firmtype != FW_TYPE_UNKNOWN) {
		GetFsFullPathName(punit->ftype, filename, MAX_PATH, fw_name[firmtype]);
		fresult = f_open(&(punit->file), filename, FA_READ);
		if (fresult == FR_OK) {
			long filesize = f_size(&(punit->file));
			fresult = f_read(&(punit->file), (void*)addr, filesize, NULL);
			f_close(&(punit->file));
		}
		if (fresult != FR_OK) {
			firmtype = FW_TYPE_UNKNOWN;
		}
	}

#endif

	return firmtype;

}
#endif
static void decode_stream_changed(struct SESSION *session)
{
	int res;
	video_stream_info_t vinfo;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	vinfo.width = g_frame_width;
	vinfo.height = g_frame_height;
#endif

	res = SessionCommand(punit->session_display, SSCMD_DISPLAY_VIDEO_STREAM_CHANGED, &vinfo);

}

static psysbuf_t decode_frame_changed(psysbuf_t buf, struct SESSION *session)
{
	int res;
	session_buffer_t *sbf = NULL;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return buf;

	buf->flags = (buf->flags & SYS_BUF_FLAG_MARK) | (uint8_t)(punit->streamtype);

	sbf = SessionBufferPush(punit->session_display, buf);
	if (sbf == NULL)
		return buf;

	return NULL;
}

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
static irq_desc malidec_irq;

static void malidec_irq_handler(session_unit_t *punit)
{
	struct SESSION *session = punit->session;

	cv_comm_irq();

	arch_irq_clear(IRQ_VIDEO);
}
#endif
//////////////////////////////////////////////////////////////////////////////////////////

static long SESSIONAPI(StreamLoad)(session_unit_t *punit)
{
	uint8_t *ldat;
	long l2, off, ret = 0;
	psysbuf_t load_buf;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	psysbuf_t buf = punit->loadbuf_video_play;
	if (buf == NULL) {
		buf = sysbuf_alloc(SYSBUF_GROUP_CV_FRMBUFS);
		if (buf != NULL) {
			punit->loadbuf_video_play = buf;
		} else {
			return -1;
		}
	} else {
	}
#endif
	load_buf = punit->load_buf;
	if (load_buf == NULL) {
		load_buf = sysbuf_alloc(SYSBUF_GROUP_CV_BITBUFS);
		if (load_buf == NULL)
			return -1;
		punit->load_buf = load_buf;
	}

	load_buf->flags &= ~SYS_BUF_FLAG_EOF;

	if (punit->load_size > 0)
	{
		l2 = load_buf->maxsize;
		if (l2 >= punit->load_size) {
			l2 = punit->load_size;
			load_buf->flags |= SYS_BUF_FLAG_EOF;
		}

		f_read(&(punit->file), (void*)HWADDR(load_buf->haddr), l2, NULL);
		load_buf->size = l2;
		punit->load_offset += l2;
		punit->load_size -= l2;

		l2 = punit->load_offset - punit->load_szcount;
		if ((l2 > punit->load_size / 100L) || (l2 > 0x40000)) {
			punit->load_szcount = punit->load_offset;
			debug("\t\tProcess : %f%%, 0x%lx (%ld) bytes\n",
			      100.0f*(float)punit->load_offset/(float)(punit->load_offset+punit->load_size),
			      punit->load_offset, punit->load_offset
			     );
		}

	}
	else
	{
		psysbuf_t buf = SessionBufferPop(punit->session);
		if (buf) {

			if (buf->list.group == load_buf->list.group) {
				load_buf = buf;
			}
			else {
				phys_addr_t src, dst;
				src = buf->haddr + buf->offset;
				dst = load_buf->haddr + load_buf->offset;
#ifdef XA_USE_EDMA_IOBUF
				edma_copy(EDMA_CHN_IO, dst, src, buf->size);
#else
				memcpy((void*)(dst), (void*)(src), buf->size);
#endif
				load_buf->size = buf->size;
				if (buf->flags & SYS_BUF_FLAG_EOF)
					load_buf->flags |= SYS_BUF_FLAG_EOF;
				load_buf->user = buf->user;
				sysbuf_free(buf);
			}
		}
		else {
			load_buf = NULL;
		}
	}

	if (load_buf) {
		ret = load_buf->size;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		punit->in_buf = load_buf;
#else
		sysbuf_put(load_buf);
#endif
		if (load_buf == punit->load_buf)
			punit->load_buf = NULL;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		port_sem_post(g_semaphore); // TODO hmm
#endif
	}

	return ret;
}

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
static void decode(struct SESSION *session, AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt)
{
	int ret;
	psysbuf_t buf;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

	ret = avcodec_send_packet(dec_ctx, pkt);
	if (ret < 0) {
		fprintf(stderr, "Error sending a packet for decoding\n");
	//	exit(1);
	}
	while (ret >= 0) {
		ret = avcodec_receive_frame(dec_ctx, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
//			fprintf(stderr, "Error during decoding\n");
//			exit(1);
			return;
		}
//		debug("v frame %3d\n", dec_ctx->frame_number);
		fflush(stdout);

		/* the picture is allocated by the decoder. no need to free it */
		buf = punit->loadbuf_video_play;
		if (buf == NULL) {
			buf = sysbuf_alloc(SYSBUF_GROUP_CV_BITBUFS);
			if (buf != NULL) {
				punit->loadbuf_video_play = buf;
			} else {
				return -1;
			}
		} else {
		}

		if (!buf)
			return;
		buf->size = 0;

		if (!imgCtx) {
			// fix the bug of 352*288 video display
			imgCtx = sws_getContext(frame->width, frame->height, punit->c->pix_fmt, frame->width, frame->height,\
									AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
			if (!imgCtx)
				return -1;

			yuvFrame = av_frame_alloc();
			int vsize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, frame->width, frame->height, 1);
			uint8_t *buf = (uint8_t *)av_malloc(vsize);
			av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize, buf, AV_PIX_FMT_YUV420P,
								frame->width, frame->height, 1);
		}

		sws_scale(imgCtx, (const uint8_t **)frame->data, frame->linesize, 0,frame->height,
					yuvFrame->data, yuvFrame->linesize);

		int y_size = frame->width * frame->height;

		if ((y_size + y_size / 2) > buf->maxsize)
			debug("frame size(%d) > buf max size(%d)!\n", y_size + y_size / 2, buf->maxsize);

		memcpy((void *)(buf->haddr + buf->size), yuvFrame->data[0], y_size);
		buf->size += y_size;
		memcpy((void *)(buf->haddr + buf->size), yuvFrame->data[1], y_size / 4);
		buf->size += y_size / 4;
		memcpy((void *)(buf->haddr + buf->size), yuvFrame->data[2], y_size / 4);
		buf->size += y_size / 4;

		punit->loadbuf_video_play = NULL;
		buf->flags |= SST_DISPLAY;

		buf->user.nHighPart = frame->pkt_dts; // save packet number in pkt_dts
		buf->user.nLowPart = frame->pkt_pts;
		decode_frame_changed(buf, session);

		/* run video part */
		if (punit->session_display != NULL) {
			SESSIONSTATE session_display = punit->session_display->SessionRun(punit->session_display);
		}
	}
}

static uint32_t lastLowPart;
static uint32_t lastHighPart;
static uint32_t nowLowPart = 0;
static uint32_t nowHighPart = 0;
#endif

#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
static int session_noirq_running(session_unit_t *punit)
{
	uint32_t flags;
	psysbuf_t load_buf;

	struct SESSION *session;
	if (punit == NULL)
		return -EACCES;

	session = punit->session;
	if (session == NULL)
		return -EFAULT;

	if(session->state <= SSTATE_INITED
		|| session->state == SSTATE_RUNNING_PAUSE )
		return 0;

	local_irq_save(flags);

	// set state
	//session->state = SSTATE_RUNNING;

	load_buf = SessionBufferPop(session);
	if (load_buf) {
		if (punit->file != NULL) {
			f_write(&(punit->file), (void*)(load_buf->haddr + load_buf->offset), load_buf->size, NULL);
		}
		sysbuf_free(load_buf);
	}
	// set state
	//session->state = SSTATE_STOP;
	session->state = SSTATE_RUNNING_IDLE;

	local_irq_restore(flags);

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;
	session_noirq_running(punit);

	return 0;
}
#endif

static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	long res = 0;
	int state;

	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

	if (session->state != SSTATE_RUNNING
		&& session->state != SSTATE_RUNNING_IDLE)
		return session->state;
#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
	session_noirq_running(punit);
#endif
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	/* polling for mali */
	res = cv_comm_wait(0);
	if (res < 0)
		punit->dumpcnt++;
	if (res >= 0 || punit->dumpcnt > 8) {
		punit->dumpcnt = 0;
	}
	else {
		return session->state;
	}

	// run state
	state = cv_session_run();

	if (punit->cvstate != state)
	{
		debug("\tcv state changed to 0x%x\n", state);
		punit->cvstate = state;
	}

	if (state & (CV_STATE_CODE_ERROR | CV_STATE_GOING_OEOF)) {
		session->state = SSTATE_STOP;
		return session->state;
	}

	if (state & CV_STATE_GOING_IDLE)
	{
		/* try to fill stream buffer */
		res = SESSIONAPI(StreamLoad)(punit);
		// control buffer put by caller, otherwise the bit_buff of mali will full
		if (SessionBufferTopNum(punit->session) < 2)
			session->state = SSTATE_RUNNING_IDLE;
	}
	else if (state & CV_STATE_GOING)
	{
		session->state = SSTATE_RUNNING;
	}
#endif

#ifndef CONFIG_MEDIA_SAVE_DEMUX_FILES
	// run state

	/* try to fill stream buffer */
	res = SESSIONAPI(StreamLoad)(punit);
	// control buffer put by caller, otherwise the bit_buff of mali will full
	if (SessionBufferTopNum(punit->session) < 2 && SessionBufferTopNum(punit->session_display) < 2)
		session->state = SSTATE_RUNNING_IDLE;
	else
		session->state = SSTATE_RUNNING;

	if (punit->in_buf && punit->in_buf->size > 1) {
		while (punit->in_buf->size > 0) {
			lastLowPart = nowLowPart;
			lastHighPart = nowHighPart;
			nowLowPart = punit->in_buf->user.nLowPart;
			nowHighPart = punit->in_buf->user.nHighPart;
			/* use the parser to split the data into frames */
			int ret = av_parser_parse2(punit->parser, punit->c, &punit->pkt->data, &punit->pkt->size,
				(const uint8_t *)punit->in_buf->haddr + punit->in_buf->offset, punit->in_buf->size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

			if (ret < 0) {
				fprintf(stderr, "Error while parsing\n");
				exit(1);
			}
			punit->in_buf->offset += ret;
			punit->in_buf->size -= ret;
			if (punit->pkt->size) {
				punit->pkt->pts = lastLowPart;
				punit->pkt->dts = lastHighPart; // save packet number in pkt_dts item
//punit->c->width = 856;
//punit->c->height = 480;
				decode(session, punit->c, punit->frame, punit->pkt);
			}

			/* run video part */
			if (punit->session_display != NULL) {
				SESSIONSTATE session_display = punit->session_display->SessionRun(punit->session_display);
				if (session_display == SSTATE_STOP) {
					session->state = SSTATE_STOP;
					return session->state;
				}
			}
		}

		if (punit->in_buf->size < 1) {
			sysbuf_free(punit->in_buf);
			punit->in_buf = NULL;
		}
	}
#endif
#ifdef MEDIA_AV_SYNC
	/* run video part */
	if (punit->session_display != NULL) {
		SESSIONSTATE session_display = punit->session_display->SessionRun(punit->session_display);
		if (session_display == SSTATE_STOP) {
			session->state = SSTATE_STOP;
			return session->state;
		}
	}
#else
	// else call the SESSIONAPI(SSCMD_STREAM_BUFCHANGED) auto
#endif

	return session->state;
}

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
	int res;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	sysbuf_group_t gp;
#endif
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || file == NULL || session->state < SSTATE_INITED)
		return -EPERM;

	punit->ftype = file->ftype;
	punit->streamtype = file->type;
#ifdef MEDIA_AV_SYNC
	punit->sync = file->sync;
#endif

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// enable interrupts
	arch_irq_clear(IRQ_VIDEO);
	arch_irq_enable(IRQ_VIDEO);

	/* open session */
	res = cv_session_open();
	if (res < 0) {
		debug("\tcv_session_open failed, code = %d ! \n", res);
		return res;
	}

	/* open firmware */
	punit->firmtype = decode_get_firmwaretype(punit->streamtype);
	if (punit->firmtype == FW_TYPE_UNKNOWN) {
		debug("\tStream type is not supported\n");
		return -ENODEV;
	}

	res = cv_firware_open(punit->firmtype);
	if (res < 0) {
		debug("\tcv_firware_open failed, code = %d ! \n", res);
		return -ENODEV;
	}

	/* load firmware */
	punit->firmtype = decode_load_firmware(punit);

	/* close firmware */
	res = cv_firware_close();
	if (res < 0) {
		debug("\tcv_firware_close failed, code = %d ! \n", res);
		return -ENODEV;
	}

	/* session start */
	res = cv_session_start();
	if (res < 0) {
		debug("\tcv_session_start failed, code = %d ! \n", res);
		return -ECHILD;
	}

	/* initial decode frame routine */
	cv_session_init_stream_flushed_function((flushed_function_t)(&decode_frame_changed), session);
	cv_session_init_stream_changed_function((changed_function_t)(&decode_stream_changed), session);

	res = cv_session_stream_open();
	if (res < 0) {
		debug("\tcv_session_stream_open failed, code = %d ! \n", res);
		cv_session_stop();
		return -EFAULT;
	}
#endif

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	switch (punit->streamtype) {
	case SST_H264:
		punit->codec_type = AV_CODEC_ID_H264;
		break;
	case SST_HEVC:
		punit->codec_type = AV_CODEC_ID_HEVC;
		break;
	case SST_MPG2:
		punit->codec_type = AV_CODEC_ID_MPEG2VIDEO;
		break;
	case SST_MPG4:
		punit->codec_type = AV_CODEC_ID_MPEG4;
		break;
	case SST_AAC:
	case SST_AAC_ADIF:
	case SST_AAC_ADTS:
	case SST_AAC_LOAS:
	case SST_AAC_LATM:
		punit->codec_type = AV_CODEC_ID_AAC;
		break;
	case SST_MP3:
		punit->codec_type = AV_CODEC_ID_MP3;
		break;
	default:
		debug("Steam type: %d not suppurt!\n", file->type);
		break;
	}

	/* find the video decoder */
	punit->pkt = av_packet_alloc();
	if (!punit->pkt)
		exit(1);

	punit->codec = avcodec_find_decoder(punit->codec_type);
	if (!punit->codec) {
		fprintf(stderr, "Codec not found\n");
		exit(1);
	}

	punit->parser = av_parser_init(punit->codec->id);
	if (!punit->parser) {
		fprintf(stderr, "parser not found\n");
		exit(1);
	}

	punit->c = avcodec_alloc_context3(punit->codec);
	if (!punit->c) {
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}

	/* For some codecs, such as msmpeg4 and mpeg4, width and height
	   MUST be initialized there because this information is not
	   available in the bitstream. */
	/* open it */
	if (avcodec_open2(punit->c, punit->codec, NULL) < 0) {
		fprintf(stderr, "Could not open codec\n");
		exit(1);
	}

	imgCtx = NULL;
	yuvFrame = NULL;

	punit->frame = av_frame_alloc();
	if (!punit->frame) {
		fprintf(stderr, "Could not allocate video frame\n");
		exit(1);
	}
#endif
	/* Initial loading */
	punit->load_buf = NULL;
	punit->load_size = 0;
	punit->load_offset = 0;
	punit->load_szcount = 0;

	punit->session_display = SessionGet(SST_DISPLAY);

	if (file->filename) {
		if (f_open(&(punit->file), file->filename, FA_READ) != FR_OK)
		{
			debug("\topen stream file error \n");
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
			cv_session_stop();
#endif
			return -EBADF;
		}
		else
		{
			punit->load_size = f_size(&(punit->file));
		}
	}

#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
	TCHAR *filename;
	// initial
	switch(file->type)
	{
	case SST_JPEG:
		{
			filename = "jpegdec.jpg";
		}break;
	case SST_H264:
		{
			filename = "h264dec.h264";
		}break;
	case SST_HEVC:
		{
			filename = "hevcdec.hevc";
		}break;
	case SST_MPG2:
		{
			filename = "mpeg2dec.m2v";
		}break;
	case SST_MPG4:
		{
			filename = "mpeg4dec.m4v";
		}break;
	case SST_RV:
		{
			filename = "rvdec.dat";
		}break;
	case SST_VC1:
		{
			filename = "vc1dec.dat";
		}break;
	case SST_VP8:
		{
			filename = "vp8dec.dat";
		}break;

	default:
		filename = "videodec.dat";
	};

	if (filename) {
		if (f_open(&(punit->file), filename, FA_WRITE|FA_CREATE_ALWAYS) !=  FR_OK)
		{
			debug("create video codec output file error \n");
		}
	}
#endif
#ifndef CONFIG_MEDIA_SAVE_DEMUX_FILES
	if (punit->session_display != NULL) {
		session_file_t fl;
		fl.type = file->type;
		fl.ftype = file->ftype;
		fl.tv = file->tv;
		fl.ta = file->ta;
		fl.filename = NULL;//file->filename;
#ifdef MEDIA_AV_SYNC
		fl.sync = file->sync;
#endif
		res = SessionCommand(punit->session_display, SSCMD_STREAM_START, &fl);
		if (res) {
			punit->session_display = NULL;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
			if (punit->file)
				f_close(&punit->file);
#else
			f_close(&punit->file);
#endif
			return res;
		}
	}
#endif

	SessionBufferDeInit(session);

#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
	session->state = SSTATE_RUNNING_IDLE;
#else
	session->state = SSTATE_RUNNING;
#endif
	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL || session->state < SSTATE_RUNNING)
		return -EPERM;

	// set state
	session->state = SSTATE_STOP;

	/* stop all */
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	av_parser_close(punit->parser);
	avcodec_free_context(&punit->c);
	av_frame_free(&punit->frame);
	av_packet_free(&punit->pkt);
	sws_freeContext(imgCtx);
	av_frame_free(&yuvFrame);

	if(punit->loadbuf_video_play)
		sysbuf_free(punit->loadbuf_video_play);
	punit->loadbuf_video_play = NULL;
#else
	cv_session_stop();
#endif
	if (punit->session_display != NULL) {
		int res = SessionCommand(punit->session_display, SSCMD_STREAM_STOP, NULL);
		if (res) {
		}
		punit->session_display = NULL;
	}

	if (punit->load_buf)
		sysbuf_free(punit->load_buf);
	punit->load_buf = NULL;

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	if (punit->file)
		f_close(&punit->file);
#else
	f_close(&punit->file);
#endif
	punit->streamtype = SST_NONE;

	// stop irq
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	arch_irq_disable(IRQ_VIDEO);
	arch_irq_clear(IRQ_VIDEO);
#endif
	SessionBufferDeInit(session);

	// set state
	session->state = SSTATE_INITED;

	return 0;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit != NULL) {
		free(punit);
	}

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// close hardware
	arch_irq_disable(IRQ_VIDEO);
	arch_irq_clear(IRQ_VIDEO);
	arch_irq_free(IRQ_VIDEO);
#endif
	debug("SessionDeInit : %s ..\n", session->name);

	session->handle = NULL;
}

static
int SESSIONAPI(SSCMD_STREAM_PAUSE)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	// Mali MVE dose not support a paused state, not schedule jobs for the MVE is the pause
	if(session->state == SSTATE_RUNNING
		|| session->state == SSTATE_RUNNING_IDLE )
		session->state = SSTATE_RUNNING_PAUSE;
	if (punit->session_display != NULL)
		SessionCommand(punit->session_display, SSCMD_STREAM_PAUSE, NULL);

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_RESUME)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	if(session->state == SSTATE_RUNNING_PAUSE)
		session->state = SSTATE_RUNNING;
	if (punit->session_display != NULL)
		SessionCommand(punit->session_display, SSCMD_STREAM_RESUME, NULL);

	return 0;
}

#ifdef MEDIA_AV_SYNC
static
int SESSIONAPI(SSCMD_STREAM_SKIP)(struct SESSION *session)
{
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

#ifdef MEDIA_SKIP_NOT_HW_RESET
	// reinit SessionBuffer
	SessionBufferDeInit(session);

	/* initialise buffers */
	cv_map_initbufs();

	// RESET register 0x0050, reset host controllor, used for skip video
	cv_port_reset(); // use reset command, the follwing frame should be an I-frame
#else
	/* stop all */
	cv_session_stop();

	// stop irq
	arch_irq_disable(IRQ_VIDEO);
	arch_irq_clear(IRQ_VIDEO);
#	if 0
	arch_irq_free(IRQ_VIDEO);
	SessionBufferInit(session);
#	endif
	SessionBufferDeInit(session);

	// install interrupts
	malidec_irq.handler = (irq_handler_t)(&malidec_irq_handler);
	malidec_irq.params = (void*)punit;
	arch_irq_request(IRQ_VIDEO, &malidec_irq);

	// enable interrupts
	arch_irq_clear(IRQ_VIDEO);
	arch_irq_enable(IRQ_VIDEO);

	FRESULT fresult = f_open(&(punit->file), punit->filename, FA_READ);

	/* open session */
	res = cv_session_open();
	if (res < 0) {
		debug("\tcv_session_open failed, code = %d ! \n", res);
		f_close(&(punit->file));
		return res;
	}

	/* open firmware */
	res = cv_firware_open(punit->firmtype);
	if (res < 0) {
		debug("\tcv_firware_open failed, code = %d ! \n", res);
		f_close(&(punit->file));
		return -ENODEV;
	} else {
		long filesize = f_size(&(punit->file));
		addr = HWADDR(g_cv_map[CV_MAP_FIRMWARE].start) + MMU_PAGE_SIZE;
		f_read(&(punit->file), (void*)addr, filesize, NULL);
		f_close(&(punit->file));
	}

	/* close firmware */
	res = cv_firware_close();
	if (res < 0) {
		debug("\tcv_firware_close failed, code = %d ! \n", res);
		return -ENODEV;
	}

	/* session start */
	res = cv_session_start();
	if (res < 0) {
		debug("\tcv_session_start failed, code = %d ! \n", res);
		return -ECHILD;
	}

	/* initial decode frame routine */
	cv_session_init_stream_flushed_function((flushed_function_t)(&decode_frame_changed), session);
	cv_session_init_stream_changed_function((changed_function_t)(&decode_stream_changed), session);

	res = cv_session_stream_open();
	if (res < 0) {
		debug("\tcv_session_stream_open failed, code = %d ! \n", res);
		cv_session_stop();
		return -EFAULT;
	}
#endif

	if (punit->session_display != NULL)
		SessionCommand(punit->session_display, SSCMD_STREAM_SKIP, NULL);
#endif
	return 0;
}
#endif

static
int SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(struct SESSION *session, uint32_t *psz)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	if (psz) {
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
		*psz = 0;
#else
		*psz = CV_BITBUFS_SIZE;
#endif
	}

	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
	case SSCMD_STREAM_START:
	{
		debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
	}break;
	case SSCMD_STREAM_PAUSE:
	{
		debug("Session '%s' command  'SSCMD_STREAM_PAUSE'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_PAUSE)(session);
	}break;
	case SSCMD_STREAM_RESUME:
	{
		debug("Session '%s' command  'SSCMD_STREAM_RESUME'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_RESUME)(session);
	}break;
	case SSCMD_STREAM_STOP:
	{
		debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);
		return SESSIONAPI(SSCMD_STREAM_STOP)(session);
	}break;
	case SSCMD_STREAM_BUFCHANGED:
	{
#ifdef CONFIG_MEDIA_SAVE_DEMUX_FILES
		return SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(session);
#endif
	}break;
	case SSCMD_STREAM_SKIP:
	{
#ifdef MEDIA_AV_SYNC
		return SESSIONAPI(SSCMD_STREAM_SKIP)(session);
#endif
	}break;
	case SSCMD_STREAM_GETVALIDINSIZE:
	{
		return SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(session, (uint32_t*)params);
	}break;
	default:
	{
		debug("Session '%s' command %d unknown !\n", session->name, cmd);
		ret = -EINVAL;
	};
	};

	return ret;
}

static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
	session_unit_t *punit = (session_unit_t *)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));
	punit->session = session;
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// install interrupts
	arch_irq_disable(IRQ_VIDEO);
	arch_irq_clear(IRQ_VIDEO);

	malidec_irq.handler = (irq_handler_t)(&malidec_irq_handler);
	malidec_irq.params = (void*)punit;

	arch_irq_request(IRQ_VIDEO, &malidec_irq);
#endif
	SessionBufferInit(session);

	session->handle = (SESSIONHANDLE)punit;
	return session->handle;
}

struct SESSION gSession_malidecoder =
{ "malidecoder", (1 << SST_JPEG)
		|(1 << SST_H264)
		|(1 << SST_HEVC)
		|(1 << SST_MPG2)
		|(1 << SST_MPG4)
		|(1 << SST_RV)
		|(1 << SST_VC1)
		|(1 << SST_VP8),
&SESSIONAPI(SessionInit),
};
